package com.wuhao.code.check.completion

import com.aegis.kotlin.containsAny
import com.aegis.mybatis.xmlless.annotations.UseEntityConvert
import com.intellij.openapi.util.TextRange
import com.intellij.patterns.PlatformPatterns.psiElement
import com.intellij.psi.*
import com.intellij.psi.util.parentsOfType
import com.intellij.psi.xml.XmlTag
import com.intellij.psi.xml.XmlToken
import com.intellij.util.ProcessingContext
import com.wuhao.code.check.hasAnnotation
import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespace

/**
 * @author 吴昊
 * @date 2024/02/01
 * @version 1.0
 * @since v1.0.0
 */
class XmlReferenceContributor : PsiReferenceContributor() {

  companion object {
    fun findQuoteTag(mapperTag: XmlTag, id: String): XmlTag? {
      var quotedTag: XmlTag? = null
      mapperTag.accept(object : XmlRecursiveElementVisitor() {

        override fun visitXmlTag(tag: XmlTag) {
          if (tag.name == "include" && tag.getAttributeValue("refid") == id) {
            quotedTag = tag.parentsOfType<XmlTag>().find {
              it.getAttributeValue("id") != null
            }
          }
          super.visitXmlTag(tag)
        }

      })
      return quotedTag
    }
  }

  override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
    registrar.registerReferenceProvider(
        psiElement(XmlToken::class.java),
        object : PsiReferenceProvider() {

          override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> {
            if (element !is XmlToken) {
              return arrayOf()
            }
            val mapperTag = element.parentsOfType<XmlTag>().find {
              it.name == "mapper"
            } ?: return arrayOf()
            val mapperClassName = mapperTag.getAttributeValue("namespace") ?: return arrayOf()
            val mapperClass = JavaPsiFacade.getInstance(element.project).findClass(
                mapperClassName, element
                .resolveScope
            ) ?: return arrayOf()
            val tag = element.parentsOfType<XmlTag>().find { it.getAttributeValue("id") != null } ?: return arrayOf()
            val id = tag.getAttributeValue("id") ?: return arrayOf()
            if (tag.name == "sql") {
              val quotedTag: XmlTag? = findQuoteTag(mapperTag, id)
              if (quotedTag != null) {
                return process(mapperClass, quotedTag.getAttributeValue("id")!!, element)
              }
            }
            return process(mapperClass, id, element)
          }

        }
    )
  }

  private fun process(
      mapperClass: PsiClass,
      id: String,
      element: XmlToken
  ): Array<PsiReference> {
    val method = mapperClass.allMethods.find { it.name == id } ?: return arrayOf()
    if (!method.hasAnnotation(UseEntityConvert::class.java)) {
      return arrayOf()
    }
    if (element.text.contains(".")) {
      val textRange = TextRange(element.text.indexOf(".") + 1, element.textLength)
      return arrayOf(ClassFieldReference(element, textRange))
    } else if (isAfterFromOrJoin(element)) {
      return arrayOf(ClassNameReference(element, TextRange(0, element.textLength)))
    }
    return arrayOf()
  }

}

fun isAfterFromOrJoin(element: XmlToken): Boolean {
  if (element.text.containsAny(".", "(", ")")) {
    return false
  }
  val prev = element.getPrevSiblingIgnoringWhitespace()
  return prev != null && prev.text.lowercase() in listOf("from", "join")
}
